#include <Master.hpp>
#include <iostream>
#include <Trie.hpp>
#include <sstream>
#include <Utility.hpp>
#include <cstdlib>
#include <ctime>
#include <GTrie.hpp>
#include <mpi.h>
#include <list>
#include <vector>

Master::Master(std::istream& source, int np, int ext) {
	this->source = &source;
	this->np 	 = np;
	this->ext    = ext;
}

void Master::run() {

	int vtime = time(0);

	MPI::COMM_WORLD.Bcast(&vtime, 1, MPI_INT, 0);

	srand(vtime);

	Trie * trie = new Trie();
	int qtyWords = trie->deserialize(*source);

	std::cerr << "There are " << qtyWords << 
		" distinct words\n";

	std::vector<std::string> words;

	trie->itemize(words);

	std::vector<int> remaining(words.size());

	delete trie;
	trie = new Trie();
	GTrie<int>  * gtrie = new GTrie<int>();

	Utility::split(trie, gtrie, words, remaining);

	int qtyParts;
	{
		std::stringstream ss;
		trie->serialize(ss);
		std::string str;
		ss >> str;
		int sz = str.size();

		char * buffer = new char[sz+1];

		buffer[sz] = 0;
		for (int i = 0; i < sz; i++)
			buffer[i] = str[i];

		MPI::COMM_WORLD.Bcast(&sz, 1, MPI_INT, 0);
		MPI::COMM_WORLD.Bcast(buffer, sz, MPI_CHAR, 0);

		std::stringstream ss2;
		ss2 << std::string(buffer);

		delete buffer;

		qtyParts = trie->deserialize(ss2);
	}

	std::vector<std::string> parts;

	trie->itemize(parts);

	std::cerr << "There are " << qtyParts << 
		" distinct parts\n";

	// delete trie;
	// for (int proc = 1; proc < np; proc++) {
	// 	trie = new Trie();

	// 	for (int i = proc, k = np;
	// 	 	 i < qtyParts; i += k) {
	// 		trie->insert(parts[i]);
	// 	}

	// 	std::stringstream ss;
	// 	trie->serialize(ss);

	// 	std::string str;
	// 	ss >> str;

	// 	int sz = str.size();

	// 	MPI::COMM_WORLD.Send(&sz, 1, MPI_INT, proc, 1);
	// 	MPI::COMM_WORLD.Send(str.c_str(), sz,
	// 						 MPI_CHAR, proc, 1);

	// 	delete trie;
	// }

	// trie = new Trie();
	int myqty = qtyParts;
	// for (int i = 0, k = np; i < qtyParts; i += k)
	// 	trie->insert(parts[i]), myqty++;

	std::cerr << "Master received "
		<< myqty << " parts\n";

	int tryc = 0;
	Trie * foundTrie = new Trie();

	
	double nextTarget = 0.1;
	int foundW;

	for (int found = 0;; ) {

		if (tryc == ext || found >= myqty) {
			
			tryc = 0;

			std::stringstream ss;

			for (int i = 1; i < np; i++) {
				int bsz;
				MPI::COMM_WORLD.Recv(&bsz, 1, MPI_INT, i, MPI_ANY_TAG);

				char * buffer = new char[bsz+1];
				buffer[bsz] = 0;

				MPI::Status status;
				for (int st = 0; st < bsz; ) {
					MPI::COMM_WORLD.Recv(buffer, bsz,
						MPI_CHAR, i, MPI_ANY_TAG, status);
					st += status.Get_count(MPI_CHAR);
				}

				ss << std::string(buffer);
				delete buffer;
				foundTrie->deserialize(ss);
			}

			foundTrie->serialize(ss);
			std::string str;
			ss >> str;
			int sz = str.size();

			char * buffer = new char[sz+1];
			buffer[sz] = 0;
			for (int i = 0; i < sz; i++)
				buffer[i] = str[i];

			MPI::COMM_WORLD.Bcast(&sz, 1, MPI_INT, 0);
			MPI::COMM_WORLD.Bcast(buffer, sz, MPI_CHAR, 0);
			delete buffer;

			std::list<std::string> list;
			foundTrie->itemize(list);
			std::list<std::string>::iterator it, itEnd;
			it = list.begin();
			itEnd = list.end();

			int helped = 0;
			for (; it != itEnd; it++) {
				std::vector<std::string> search = 
					trie->retrieve(*it);
				if (search.size()) {
					helped++;
					found++;
					trie->remove(*it);

					std::vector<int> connections = 
						gtrie->retrieve(word);

					for(int i = 0; i < connections.size(); i++) {
						remaining[connections[i]]--;
						if (!remaining[connections[i]]) {
							foundW ++;
							while (foundW >= qtyWords * nextTarget) {
								std::cerr << 
									(nextTarget * 100) 
									<< " percent reached at " << 
									MPI::Wtime() << std::endl;
								nextTarget += 0.1;
							}

							std::cout << words[connections[i]] <<
								std::endl;
						}
					}

					gtrie->remove(word);

				}
			}
			std::cerr << "Master helped " << helped <<
			 " - " << found << "\n";

			delete foundTrie;
			foundTrie = new Trie();

			if (found >= myqty) break;
		}

		std::string word = Utility::randomWord(true);
		std::vector<std::string> search = 
			trie->retrieveUntil(word);

		tryc ++;
		if (search.size()) {
			found++;
			trie->remove(word);
			foundTrie->insert(word);

			std::vector<int> connections = 
				gtrie->retrieve(word);

			for(int i = 0; i < connections.size(); i++) {
				remaining[connections[i]]--;
				if (!remaining[connections[i]]) {
					foundW ++;
					while (foundW >= qtyWords * nextTarget) {
						std::cerr << 
							(nextTarget * 100) 
							<< " percent reached at " << 
							MPI::Wtime() << std::endl;
						nextTarget += 0.1;
					}

					std::cout << words[connections[i]] <<
						std::endl;
				}
			}

			gtrie->remove(word);
		}
	}

	delete trie;
	delete foundTrie;

	std::cerr << "Master finished. Waiting for others\n";

	MPI::COMM_WORLD.Barrier();

	std::cerr << "Very good!\n";

}
